package com.feature50.makeover;

import com.feature50.makeover.filthy.BlendComposite;
import com.feature50.makeover.filthy.BrightPassFilter;
import com.feature50.makeover.filthy.GaussianBlurFilter;
import com.feature50.makeover.filthy.GraphicsUtilities;
import com.feature50.makeover.utils.FontResolver;
import com.feature50.makeover.utils.ImageUtils;
import com.feature50.makeover.utils.PaintUtils;
import com.feature50.makeover.utils.Platform;
import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.forms.layout.ColumnSpec;
import com.jgoodies.forms.layout.FormLayout;
import com.jgoodies.forms.layout.Sizes;
import org.jdesktop.animation.timing.Animator;
import org.jdesktop.animation.timing.TimingTargetAdapter;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

public class ButtonTabPanel extends JComponent {
    private static final int DEFAULT_BAR_HEIGHT = 31;

    private static final FontResolver.FontInfo[] FONT_DEFAULT = new FontResolver.FontInfo[] {
            new FontResolver.FontInfo("Franklin Gothic Medium", 14)
    };

    private static final BufferedImage IMG_CLOSE_BUTTON = ImageUtils.getImage("icn_clear.png");
    private static final BufferedImage IMG_CLOSE_BUTTON_PRESSED = ImageUtils.getImage("icn_clear_pressed.png");

    private List<AbstractButton> buttons = new ArrayList<AbstractButton>();
    private Map<String, SlidePanel> panels = new LinkedHashMap<String, SlidePanel>();
    private List<AbstractButton> panelButtons = new ArrayList<AbstractButton>();
    private ButtonGroup panelGroup = new ButtonGroup();

    private boolean drawGrayBuffer = true;
    private JPanel tabPanel;
    private JPanel buttonPanel;
    private FormLayout tabPanelLayout;
    private JComponent target;

    private BrightPassFilter brightPassFilter = new BrightPassFilter();
    private float smoothness = 4.0f;
    private static final String PANEL_KEY = "clarity.tabbutton.panel";

    public ButtonTabPanel(JComponent target) {
        this.target = target;
        init();
    }

    private void init() {
        buttonPanel = new JPanel();
        buttonPanel.setOpaque(false);
        buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS));
        buttonPanel.setBorder(BorderFactory.createEmptyBorder(1, 0, 0, 0));

        tabPanel = new JPanel();
        tabPanel.setOpaque(false);
        tabPanelLayout = new FormLayout("default", "1px, fill:default:grow");
        tabPanel.setLayout(tabPanelLayout);

        layoutPanels();

        target.setLayout(new GridLayout(1, 1));
    }

    public TabButton addTabPanel(String name, final JComponent initialContents, final boolean showImmediately) {
        if (panels.containsKey(name)) {
            int counter = 2;
            String newName;
            do {
                newName = name + " (" + counter++ + ")";
            } while (panels.containsKey(newName));
            name = newName;
        }

        SlidePanel container = new SlidePanel(initialContents, new Runnable() {
            public void run() {
                if (initialContents instanceof AppPanel) {
                    AppPanel ap = (AppPanel) initialContents;
                    ap.panelDisplayed();
                }
            }
        }, new Runnable() {
            public void run() {
                if (panelButtons.size() > 0) {
                    panelButtons.get(0).doClick();
                } else {
                    target.removeAll();
                    target.revalidate();
                    target.repaint();
                }
            }
        });
        panels.put(name, container);

        final TabButton button = new TabButton(name);
        button.putClientProperty(PANEL_KEY, name);
        button.setBuilt(0f);
        final String tempName = name;
        button.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                showPanel(tempName);
            }
        });
        panelGroup.add(button);

        if (panelButtons.size() > 0) tabPanelLayout.appendColumn(ColumnSpec.decode("default"));
        panelButtons.add(button);
        tabPanel.add(button, new CellConstraints().xy(panelButtons.size(), 2));

        // get a copy of what the app is showing right now
        BufferedImage img = GraphicsUtilities.createCompatibleImage(target.getWidth(), target.getHeight());
        Graphics2D g2d = img.createGraphics();
        target.print(g2d);
        g2d.dispose();

        container.setBackgroundImage(img);

        if (showImmediately) {
            button.doClick();
        }

        final Animator animator = new Animator(1000, new TimingTargetAdapter() {
            public void begin() {
                tabPanel.revalidate();
                tabPanel.repaint();
            }

            public void timingEvent(float v) {
                button.setBuilt(v);
                button.repaint();
            }

            public void end() {
                button.setBuilt(1f);
            }
        });
        animator.setAcceleration(0.2f);
        animator.setDeceleration(0.5f);
        animator.start();

        container.showPanel();

        return button;
    }

    public void removeTabPanel(TabButton tabButton) {
        panelGroup.remove(tabButton);
        tabPanel.remove(tabButton);
        panelButtons.remove(tabButton);
        buttonPanel.setEnabled(false);

        BufferedImage img = null;
        if (panelButtons.size() > 0) {
            AbstractButton button = panelButtons.get(0);

            SlidePanel newPanel = panels.get(button.getClientProperty(PANEL_KEY));
            target.add(newPanel);
            newPanel.setBounds(0 - target.getWidth(), 0, target.getWidth(), target.getHeight());

            img = GraphicsUtilities.createCompatibleImage(target.getWidth(), target.getHeight());
            Graphics2D g2d = img.createGraphics();
            newPanel.print(g2d);
            g2d.dispose();
            target.remove(newPanel);
        }

        SlidePanel panel = panels.remove(tabButton.getClientProperty(PANEL_KEY));
        panel.setBackgroundImage(img);
        panel.hidePanel();
    }

    public void addButton(AbstractButton button) {
        buttons.add(button);
        buttonPanel.add(button);
    }

    private void showPanel(String name) {
        target.removeAll();

        JPanel panel = panels.get(name);
        if (panel != null) target.add(panel);

        target.revalidate();
        target.repaint();
    }

    private void layoutPanels() {
        String grayBufferRow = (drawGrayBuffer) ? ", " + PaintUtils.GRAY_BUFFER_HEIGHT + "px" : "";
        FormLayout layout = new FormLayout("4dlu, default, 4dlu, default, fill:default:grow", "fill:default:grow" + grayBufferRow);
        setLayout(layout);

        CellConstraints cc = new CellConstraints();
        add(buttonPanel, cc.xy(2, 1));
        add(tabPanel, cc.xy(4, 1));
    }

    public boolean isOpaque() {
        return true;
    }

    protected void paintComponent(Graphics g) {
        PaintUtils.paintCandylandBackground(g, getHeight(), getWidth(), true);
    }

    public Dimension getPreferredSize() {
        int height = DEFAULT_BAR_HEIGHT + PaintUtils.GRAY_BUFFER_HEIGHT;

        // todo: calculated preferred width

        return new Dimension(20, height);
    }

    public Dimension getMinimumSize() {
        return getPreferredSize();
    }

    public Dimension getMaximumSize() {
        return getPreferredSize();
    }

    public class TabButton extends JToggleButton {
        private BufferedImage selectedImage;
        private BufferedImage highlight;

        private List<ActionListener> closeListeners = new ArrayList<ActionListener>();

        private float built = 1f;

        private int closeSpacer = 5;

        private boolean over = false;

        public TabButton(String text) {
            super(text);
            init();
        }

        private void init() {
            Font font = FontResolver.getFont(FONT_DEFAULT);
            setFont(font);
            setForeground(Color.WHITE);
            setBorder(BorderFactory.createEmptyBorder(0, Sizes.dialogUnitXAsPixel(4, this), 0, Sizes.dialogUnitXAsPixel(4, this)));

            addMouseListener(new MouseAdapter() {
                public void mouseEntered(MouseEvent e) {
                    over = true;
                    repaint();
                }

                public void mouseExited(MouseEvent e) {
                    over = false;
                    repaint();
                }

                public void mouseClicked(MouseEvent e) {
                    Rectangle rect = new Rectangle(
                            getWidth() - getInsets().right - closeSpacer - IMG_CLOSE_BUTTON.getWidth(),
                            (getHeight() / 2) - (IMG_CLOSE_BUTTON.getHeight() / 2),
                            IMG_CLOSE_BUTTON.getWidth(),
                            IMG_CLOSE_BUTTON.getHeight()
                    );
                    if (rect.contains(e.getPoint())) {
                        fireClose();
                    }
                }
            });
        }

        private void fireClose() {
            ActionEvent event = new ActionEvent(this, -1, "close");
            for (int i = 0; i < closeListeners.size(); i++) {
                ActionListener listener = closeListeners.get(i);
                listener.actionPerformed(event);
            }
        }


        public float getBuilt() {
            return built;
        }

        public void setBuilt(float built) {
            this.built = built;
        }

        public boolean isOpaque() {
            return false;
        }

        public Dimension getMinimumSize() {
            return getPreferredSize();
        }

        public Dimension getPreferredSize() {
            int width = getInsets().left + getInsets().right;
            int height = getInsets().top + getInsets().bottom;

            // check if this button is left-most; if so, it'll have a special one-pixel border
            if (hasSpecialLeftBorder()) width += 1;

            // check if this button is right-most
            if (hasSpecialRightBorder()) width += 1;

            // the normal inner border that's always present
            width += 2;

            Graphics2D g2d = (Graphics2D) getGraphics().create();
            Rectangle2D bounds = g2d.getFontMetrics().getStringBounds(getText(), g2d);
            width += (int) bounds.getWidth();
            height += (int) bounds.getHeight();
            g2d.dispose();

            width += IMG_CLOSE_BUTTON.getWidth();
            width += closeSpacer;

            return new Dimension(width, height);
        }

        private boolean hasSpecialRightBorder() {
            return (panelButtons.indexOf(this)) == panelButtons.size() - 1;
        }

        private boolean hasSpecialLeftBorder() {
            return (panelButtons.indexOf(this)) == 0;
        }

        public Dimension getMaximumSize() {
            return getPreferredSize();
        }

        public void setText(String text) {
            super.setText(text);
            selectedImage = null;
        }

        protected void paintBorder(Graphics g) {}

        protected void paintComponent(Graphics g) {
            g = g.create();
            if (built != 1f) {
                int width = getWidth();
                float diff = 1f - built;
                int x = (int) (width * -diff);
                g.translate(x, 0);
            }

            if (selectedImage != null) {
                if ((getHeight() != selectedImage.getHeight()) || (getWidth() != selectedImage.getWidth())) selectedImage = null;
            }

            boolean paintImage = false;

            if ((isSelected()) && (selectedImage == null)) {
                selectedImage = GraphicsUtilities.createCompatibleImage(getWidth(), getHeight());
                paintImage = true;
            }

            Graphics2D g2d = null;
            if (isSelected()) {
                if (paintImage) g2d = selectedImage.createGraphics();
            } else {
                g2d = (Graphics2D) g.create();
            }

            if (g2d != null) {
                g2d.setFont(getFont());

                // draw the background
                int width = getWidth();
                int height = getHeight();
                int x = 0;
                int y;

                if (hasSpecialLeftBorder()) {
                    x += 1;
                    width -= 1;
                }
                if (hasSpecialRightBorder()) width -= 1;

                if (isSelected()) {
                    g2d.setColor(new Color(147, 147, 146));
                    g2d.drawLine(x, 0, width, 0);

                    g2d.setColor(new Color(124, 124, 124));
                    g2d.drawLine(x, 1, width, 1);

                    int midpoint = height / 2;
                    int temp_height = midpoint - 2;
                    g2d.setPaint(new GradientPaint(0, 2, new Color(103, 103, 103), 0, midpoint, new Color(76, 77, 77)));
                    g2d.fillRect(x, 2, width, temp_height);

                    g2d.setColor(new Color(53, 53, 53));
                    g2d.drawLine(x, midpoint, width, midpoint);

                    temp_height = height - (midpoint + 1);
                    g2d.setPaint(new GradientPaint(0, midpoint + 1, new Color(36, 36, 36), 0, midpoint + 1 + temp_height, new Color(72, 72, 72)));
                    g2d.fillRect(x, midpoint + 1, width, temp_height);

//                    if (highlight == null) highlight = ImageUtils.getFrameworkImage("header-halo.png");
//                    if (highlight != null) {
//                        g2d.drawImage(highlight, 0, 0, getWidth(), getHeight(), null);
//                    }
                }

                if (hasSpecialLeftBorder()) {
                    g2d.setColor(new Color(0, 0, 0, 64));
                    g2d.drawLine(0, 0, 0, getHeight() - 2);
                }
                if (hasSpecialRightBorder()) {
                    g2d.setColor(new Color(255, 255, 255, 64));
                    g2d.drawLine(getWidth() - 1, 0, getWidth() - 1, getHeight() - 2);
                }

                g2d.setColor(new Color(255, 255, 255, 64));
                g2d.drawLine(x, 0, x, getHeight() - 2);
                g2d.setColor(new Color(0, 0, 0, 64));
                g2d.drawLine(width, 0, width, getHeight() - 2);

                // paint text
                int textWidth = (int) g2d.getFontMetrics().getStringBounds(getText(), g2d).getWidth();
                int textHeight = g2d.getFontMetrics().getAscent();
                x = ((getWidth() - closeSpacer - IMG_CLOSE_BUTTON.getWidth()) / 2) - (textWidth / 2);

                y = (getHeight() / 2) + (textHeight / 2);

                y -= 2; // Franklin Gothic Medium looks a bit better with a higher baseline 

                Platform.setAntialiasing(g2d, getFont());
                g2d.setColor((isSelected()) ? Color.WHITE : new Color(148, 148, 148));
                g2d.drawString(getText(), x, y);

                g2d.dispose();

                //if (isSelected()) selectedImage = bloomImage(selectedImage);
            }

            if (isSelected()) g.drawImage(selectedImage, 0, 0, null);

            if (over) {
                int x = getWidth() - getInsets().right - IMG_CLOSE_BUTTON.getWidth();
                int y = (getHeight() / 2) - (IMG_CLOSE_BUTTON.getHeight() / 2);

                Image img = getModel().isPressed() ? IMG_CLOSE_BUTTON_PRESSED : IMG_CLOSE_BUTTON;
                g.drawImage(img, x, y, null);
            }

            g.dispose();
        }

        public void addCloseActionListener(ActionListener listener) {
            closeListeners.add(listener);
        }

        public void removeCloseActionListener(ActionListener listener) {
            closeListeners.remove(listener);
        }
    }

    private BufferedImage bloomImage(BufferedImage image) {
        BufferedImage result = image;

        if (smoothness > 1.0f) {
            result = GraphicsUtilities.createThumbnailFast(image, (int) (image.getWidth() / smoothness));
        }

        BufferedImage brightPass = brightPassFilter.filter(result, null);
        GaussianBlurFilter gaussianBlurFilter = new GaussianBlurFilter(5);

        BufferedImage bloom = GraphicsUtilities.createCompatibleImage(image);
        Graphics2D g2 = bloom.createGraphics();

        g2.drawImage(image, 0, 0, null);
        g2.setComposite(BlendComposite.Add);
        g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
                            RenderingHints.VALUE_INTERPOLATION_BILINEAR);

        g2.drawImage(gaussianBlurFilter.filter(brightPass, null), 0, 0, image.getWidth(), image.getHeight(), null);

//        for (int i = 0; i < 3; i++) {
//            if (brightPass.getWidth() < 3) break;
//            brightPass = GraphicsUtilities.createThumbnailFast(brightPass, brightPass.getWidth() / 2);
//            g2.drawImage(gaussianBlurFilter.filter(brightPass, null), 0, 0, image.getWidth(), image.getHeight(), null);
//        }

        g2.dispose();

        return bloom;
    }
}
